package jmine.tec.test.junit3;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import junit.framework.TestCase;

/**
 * Enhanced test case that includes a setUpBeforeClass wich is executed before any test and before the setUpBeforeTest and only once per
 * TestCase, and a tearDownAfterClass, executed after all the tests have been executed. It is not recomended to run partial tests with this
 * class because the tearDownAfterClass will never get called
 * 
 * @author takeshi
 */
public abstract class EnhancedTestCase extends TestCase {

    private static final Map<Class<?>, Integer> COUNTERS = Collections.synchronizedMap(new HashMap<Class<?>, Integer>());

    private static final Map<Class<?>, Map<Object, Object>> RESOURCES = new ConcurrentHashMap<Class<?>, Map<Object, Object>>();

    /**
     * 
     * 
     */
    public EnhancedTestCase() {
        super();
    }

    /**
     * @param name test name
     */
    public EnhancedTestCase(final String name) {
        super(name);
    }

    /**
     * @throws Exception in case of exception
     */
    @Override
    @Deprecated
    protected final void setUp() throws Exception {
        super.setUp();
        synchronized (EnhancedTestCase.class) {
            if (this.getTestCount() == -1) {
                this.countTests();
                this.setUpBeforeClass();
            }
        }
        this.setUpBeforeTest();
    }

    /**
     * Gets the test count associated with the current class
     * 
     * @return int
     */
    private int getTestCount() {
        Integer ai = COUNTERS.get(this.getClass());
        if (ai == null) {
            return -1;
        }
        return ai;
    }

    /**
     * 
     * 
     */
    private void countTests() {
        int testCount = 0;
        for (Method method : this.getClass().getMethods()) {
            if (Modifier.isPublic(method.getModifiers()) && this.isTestMethod(method)) {
                testCount++;
            }
        }
        COUNTERS.put(this.getClass(), testCount);
    }

    /**
     * Checks whether this method is a test
     * 
     * @param method the method
     * @return true if its a test
     */
    private boolean isTestMethod(final Method method) {
        String name = method.getName();
        Class<?>[] parameters = method.getParameterTypes();
        Class<?> returnType = method.getReturnType();
        return parameters.length == 0 && name.startsWith("test") && returnType.equals(Void.TYPE);
    }

    /**
     * @throws Exception in case of exception
     */
    @Override
    @Deprecated
    protected final void tearDown() throws Exception {
        super.tearDown();
        try {
            this.tearDownAfterTest();
        } finally {
            synchronized (EnhancedTestCase.class) {
                if (this.decrementAndGetTestCount() == 0) {
                    this.tearDownAfterClass();
                    RESOURCES.remove(this.getClass());
                    COUNTERS.remove(this.getClass());
                }
            }
        }
    }

    /**
     * Decrements the test count and returns the value
     * 
     * @return int
     */
    private int decrementAndGetTestCount() {
        int cnt = this.getTestCount();
        cnt--;
        COUNTERS.put(this.getClass(), cnt);
        return cnt;
    }

    /**
     * @throws Exception in case of exception
     */
    protected void setUpBeforeClass() throws Exception {
        // vazio
    }

    /**
     * @throws Exception in case of exception
     */
    protected void setUpBeforeTest() throws Exception {
        // vazio
    }

    /**
     * @throws Exception in case of exception
     */
    protected void tearDownAfterClass() throws Exception {
        // vazio
    }

    /**
     * @throws Exception in case of exception
     */
    protected void tearDownAfterTest() throws Exception {
        // vazio
    }

    /**
     * Sets a shared resource among tests
     * 
     * @param key {@link Object}
     * @param resource {@link Object}
     */
    public final void setSharedResource(final Object key, final Object resource) {
        synchronized (this.getClass()) {
            this.initMap();
            RESOURCES.get(this.getClass()).put(key, resource);
        }
    }

    /**
     * Inits the map if necessary
     */
    private void initMap() {
        synchronized (this.getClass()) {
            Map<Object, Object> map = RESOURCES.get(this.getClass());
            if (map == null) {
                map = new HashMap<Object, Object>();
                RESOURCES.put(this.getClass(), map);
            }
        }
    }

    /**
     * Gets a shared resource
     * 
     * @param key the key
     * @return Object
     */
    public final Object getSharedResource(final Object key) {
        synchronized (this.getClass()) {
            Map<Object, Object> map = RESOURCES.get(this.getClass());
            if (map == null) {
                return null;
            }
            return map.get(key);
        }
    }

    /**
     * Removes a shared resurce from the shared resource pool
     * 
     * @param key the key
     */
    public final void removeSharedResource(final Object key) {
        synchronized (this.getClass()) {
            Map<Object, Object> map = RESOURCES.get(this.getClass());
            if (map != null) {
                map.remove(key);
            }
        }
    }

    /**
     * 
     */
    public final void clearAllSharedResources() {
        synchronized (this.getClass()) {
            Map<Object, Object> map = RESOURCES.get(this.getClass());
            if (map != null) {
                map.clear();
            }
        }
    }

}
